﻿#region File Description

// --------------------------------
// <copyright file="MultiAnimationPlayer.cs" company="Faculty of Information Technology, Czech Technical University in Prague">
//     This document is shared by Microsoft Public License (MS-PL)
//     For further details please see http://msdn.microsoft.com/en-us/library/ff648068.aspx or license.txt.
// </copyright>
// <author>Vladislav Richter</author>

// <email>richtvl2@fit.cvut.cz</email>

// <date>9. 11. 2011 22:22:37</date>

// <summary>
//
// </summary>
// ------------------------------------------------------------------------------------------------------

#endregion File Description

#region Using Statements

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

#endregion Using Statements

namespace ExternalClasses.Animations
{
    /// <summary>
    /// The animation player is in charge of decoding bone position
    /// matrices from an animation clip.
    /// </summary>
    public class MultiAnimationPlayer
    {
        public const string Idle = "Idle";
        public const string Walk = "Walk";
        public const string Run = "Run";

        #region Fields

        //public List<AnimationLayer> Layers;
        public List<ClipLayer> ThisCharactersAnimations;
        public Animations AnimData;

        // Current animation transform matrices.
        Matrix[] boneTransforms;
        Matrix[] worldTransforms;
        Matrix[] skinTransforms;

        // Backlink to the bind pose and skeleton hierarchy data.
        SkinningData skinningDataValue;

        #endregion Fields

        #region METHODS

        /// <summary>
        /// Constructs a new animation player.
        /// </summary>
        public MultiAnimationPlayer(Animations anim)
        {
            skinningDataValue = anim.skinningData;
            AnimData = anim;

            boneTransforms = new Matrix[skinningDataValue.BindPose.Count];
            worldTransforms = new Matrix[skinningDataValue.BindPose.Count];
            skinTransforms = new Matrix[skinningDataValue.BindPose.Count];
            skinningDataValue.BindPose.CopyTo(boneTransforms, 0);

            ThisCharactersAnimations = new List<ClipLayer>();
            for (int i = 0; i < anim.Layers.Layers.Count; i++)
            {
                ThisCharactersAnimations.Add(new ClipLayer(anim.Layers.Layers[i]));
            }
        }

        public void StartTake(string name)
        {
            StartTake(name, false);
        }

        /// <summary>
        /// Starts decoding the specified animation clip.
        /// </summary>
        public void StartTake(string name, bool Once)
        {
            AnimationTake take = AnimData.GetTake(name);
            if (take == null) return;
            ThisCharactersAnimations[take.LayerIndex].Reset(take.ClipData, Once);
        }

        /// <summary>
        /// Stops decoding the specified animation clip.
        /// </summary>
        public void StopTake(string name)
        {
            AnimationTake take = AnimData.GetTake(name);
            if (take == null) return;
            ThisCharactersAnimations[take.LayerIndex].Running = false;
        }

        /// <summary>
        /// Stops decoding every animation clip.
        /// </summary>
        public void StopAllTakes()
        {
            for (int i = 0; i < ThisCharactersAnimations.Count; i++)
                ThisCharactersAnimations[i].Running = false;
        }

        public float GetTakeProgress(string name)
        {
            AnimationTake take = AnimData.GetTake(name);
            if (take == null) return 0;
            return ThisCharactersAnimations[take.LayerIndex].Progress;
        }

        public bool IsTakeRunning(string name)
        {
            AnimationTake take = AnimData.GetTake(name);
            if (take == null) return false;
            return ThisCharactersAnimations[take.LayerIndex].Running;
        }

        /// <summary>
        /// Advances the current animation position.
        /// </summary>
        public void Update(TimeSpan time, bool relativeToCurrentTime,
                           Matrix rootTransform)
        {
            UpdateBoneTransforms(time, relativeToCurrentTime);
            UpdateWorldTransforms(rootTransform);
            UpdateSkinTransforms();
        }

        /// <summary>
        /// Helper used by the Update method to refresh the BoneTransforms data.
        /// </summary>
        public void UpdateBoneTransforms(TimeSpan time, bool relativeToCurrentTime)
        {
            for (int i = 0; i < ThisCharactersAnimations.Count; i++)
            {
                if (ThisCharactersAnimations[i].Running)
                {
                    if (ThisCharactersAnimations[i].Clip == null && ThisCharactersAnimations[i].Running) throw new InvalidOperationException("AnimationPlayer.Update was called before StartPrimaryClip");
                    ThisCharactersAnimations[i].Time = time;
                    // Update the animation position.
                    if (relativeToCurrentTime)
                    {
                        ThisCharactersAnimations[i].Time += ThisCharactersAnimations[i].ClipTime;
                        if (ThisCharactersAnimations[i].Time > ThisCharactersAnimations[i].Clip.Duration)
                        {
                            if (ThisCharactersAnimations[i].RunOnce) // If we reached the end, stop.
                            {
                                ThisCharactersAnimations[i].Progress = 1.0f;
                                ThisCharactersAnimations[i].Running = false;
                            }
                            else // If we reached the end, loop back to the start.
                            {
                                while (ThisCharactersAnimations[i].Time >= ThisCharactersAnimations[i].Clip.Duration) ThisCharactersAnimations[i].Time -= ThisCharactersAnimations[i].Clip.Duration;
                                ThisCharactersAnimations[i].Progress = (float)(ThisCharactersAnimations[i].Time.TotalSeconds / ThisCharactersAnimations[i].Clip.Duration.TotalSeconds);
                            }
                        }
                        else
                        {
                            ThisCharactersAnimations[i].Progress = (float)(ThisCharactersAnimations[i].Time.TotalSeconds / ThisCharactersAnimations[i].Clip.Duration.TotalSeconds);
                        }
                    }
                }
                if (ThisCharactersAnimations[i].Running)
                {
                    if (ThisCharactersAnimations[i].Time < TimeSpan.Zero || ThisCharactersAnimations[i].Time >= ThisCharactersAnimations[i].Clip.Duration) ThisCharactersAnimations[i].Time = TimeSpan.Zero;
                    if (ThisCharactersAnimations[i].Time < ThisCharactersAnimations[i].ClipTime) ThisCharactersAnimations[i].Keyframe = 0; // If the position moved backwards, reset the keyframe index.
                    ThisCharactersAnimations[i].ClipTime = ThisCharactersAnimations[i].Time;
                    IList<Keyframe> keyframes = ThisCharactersAnimations[i].Clip.Keyframes;
                    while (ThisCharactersAnimations[i].Keyframe < keyframes.Count)
                    {
                        Keyframe keyframe = keyframes[ThisCharactersAnimations[i].Keyframe];
                        if (keyframe.Time > ThisCharactersAnimations[i].ClipTime) break; // Stop when we've read up to the current time position.
                        if (ThisCharactersAnimations[i].Layer.ActiveBoneIndexes.Contains(keyframe.Bone)) boneTransforms[keyframe.Bone] = keyframe.Transform; // Use this keyframe.
                        ThisCharactersAnimations[i].Keyframe++;
                    }
                }
            }
        }

        #endregion METHODS

        #region UNIMPORTANT

        /// <summary>
        /// Helper used by the Update method to refresh the WorldTransforms data.
        /// </summary>
        public void UpdateWorldTransforms(Matrix rootTransform)
        {
            // Root bone.
            worldTransforms[0] = boneTransforms[0] * rootTransform;

            // Child bones.
            for (int bone = 1; bone < worldTransforms.Length; bone++)
            {
                int parentBone = skinningDataValue.SkeletonHierarchy[bone];

                worldTransforms[bone] = boneTransforms[bone] *
                                             worldTransforms[parentBone];
            }
        }

        /// <summary>
        /// Helper used by the Update method to refresh the SkinTransforms data.
        /// </summary>
        public void UpdateSkinTransforms()
        {
            for (int bone = 0; bone < skinTransforms.Length; bone++)
            {
                skinTransforms[bone] = skinningDataValue.InverseBindPose[bone] *
                                            worldTransforms[bone];
            }
        }

        /// <summary>
        /// Gets the current bone transform matrices, relative to their parent bones.
        /// </summary>
        public Matrix[] GetBoneTransforms()
        {
            return boneTransforms;
        }

        /// <summary>
        /// Gets the current bone transform matrices, in absolute format.
        /// </summary>
        public Matrix[] GetWorldTransforms()
        {
            return worldTransforms;
        }

        /// <summary>
        /// Gets the current bone transform matrices,
        /// relative to the skinning bind pose.
        /// </summary>
        public Matrix[] GetSkinTransforms()
        {
            return skinTransforms;
        }

        #endregion UNIMPORTANT
    }
}